﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.XPath;

using umbraco.blog4.spam;
using umbraco.BusinessLogic;
using umbraco.BusinessLogic.console;
using umbraco.cms.businesslogic.web;
using umbraco.DataLayer;

#if UMB45
using umbraco.presentation.nodeFactory;
#else
using umbraco.NodeFactory;
using umbraco.BasePages;
using umbraco.cms.businesslogic;
#endif

namespace umbraco.blog4 {
    public class BlogLibrary {

        public static string Dictionary(string text, string fallback) {
            string key = text.Trim().TrimStart('#');
            string result = umbraco.library.GetDictionaryItem(key);

            if (result.Length > 0) return result;
            else if(!string.IsNullOrEmpty(fallback)) return fallback;
            return key;
        }
        /// <summary>
        /// Gets the gravatar.
        /// </summary>
        /// <param name="email">The email.</param>
        /// <param name="size">The size.</param>
        /// <param name="defaultImage">The default image.</param>
        /// <returns></returns>
        public static string GetGravatar(string email, int size, string defaultImage) {
            if (IsValidEmail(email)) {
                string protocol = HttpContext.Current.Request.IsSecureConnection ? "https" : "http";
                string rating = "G";
                return HttpContext.Current.Server.HtmlEncode(string.Format("{0}://www.gravatar.com/avatar/{1}.jpg?s={2}&d={3}&r={4}",
                        protocol, // protocol
                        umbraco.library.md5(email), // MD5'd email address 
                        size.ToString(), // size in pixels
                        defaultImage, // default image
                        rating)); // rating
            } else {
                return String.Empty;
            }
        }
        /// <summary>
        /// Recursively returns all document types that <paramref name="docType"/> inherits from.
        /// </summary>
        /// <param name="docType">The document type to recursively gather.</param>
        /// <returns></returns>
        public static List<DocumentType> TypeList(string docType) {
            return TypeList(docType, "up");
        }

        public static List<DocumentType> TypeList(string docType, string direction) {
            // RJF - Fixed recursion to use the MasterContentType property instead of ParentId which is irrelevant for ContentTypes
            // (at least in 4.7.1 it is)
            var ret = new List<DocumentType>();
            DocumentType d = DocumentType.GetByAlias(docType);
            ret.Add(d);
            switch (direction) {
            	case "down":
                    if (d.HasChildren) {
                        List<DocumentType> dl = (from p in DocumentType.GetAllAsList()
                                                 where p.MasterContentType == d.Id
                                                 select p).ToList();
                        ret.AddRange(dl.Where(t => !t.HasChildren)); // those with children will be added at the next level.
                        foreach (DocumentType dx in dl.Where(t => t.HasChildren)) ret.AddRange(TypeList(dx.Alias, "down"));
                    }
            		break;
                default:
                    // Up
                    while (d.MasterContentType > 0) {
                        d = new DocumentType(d.MasterContentType);
                        ret.Add(d);
                    }
                    break;
            }
            return ret;
        }

        private readonly static Dictionary<string, List<string>> _nodeTypeCache = new Dictionary<string, List<string>>();
        internal static void ClearDocumentTypeCache() {
            _nodeTypeCache.Clear();
        }

        /// <summary>
        /// Checks whether a given node's doctype inherits from a specified doctype.
        /// </summary>
        /// <param name="docType">The parent doctype to check inheritance from.</param>
        /// <param name="nodeId">The node.</param>
        /// <returns></returns>
        public static bool IsType(string docType, int nodeId) {
            Node node = new Node(nodeId);
            string alias = node.NodeTypeAlias;
            if (string.IsNullOrEmpty(alias))
            {
                Document doc = new Document(nodeId);
                alias = doc.ContentType.Alias;
            }

            return TypeAliasInherits(docType, alias);
        }


        /// <summary>
        /// Checks whether a given node's doctype inherits from a specified doctype.
        /// </summary>
        /// <param name="docType">The parent doctype to check inheritance from.</param>
        /// <param name="alias">The Document Type Alias to check.</param>
        /// <returns></returns>
        public static bool TypeAliasInherits(string docType, string alias) {
            if (string.IsNullOrEmpty(alias))
                return false;

            if (_nodeTypeCache.ContainsKey(alias))
                return _nodeTypeCache[alias].Contains(docType);
            else
            {
                List<string> check = BlogLibrary.TypeList(alias).Select(dt => dt.Alias).ToList();
                _nodeTypeCache.Add(alias, check);

                return (check.Contains(docType));
            }
        }

        /// <summary>
        /// Retrieves a list of Blogs that the Current user has access to.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Blog> GetBlogsForCurrentUser()
        {
            return GetBlogsForUser(UmbracoEnsuredPage.CurrentUser);
        }

        /// <summary>
        /// Retrieves a list of Blogs that the Specified user has access to.
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public static IEnumerable<Blog> GetBlogsForUser(User user)
        {
            foreach (var type in TypeList("Blog", "down"))
            {
                foreach(var doc in Document.GetDocumentsOfDocumentType(type.Id).Where(d=>d.Path.Contains(user.StartNodeId.ToString())))
                    yield return new Blog(doc);
            }
        }

        public static bool IsAuthor(int nodeId, string email) {
            bool found = false;
            User[] ul = User.getAllByEmail(email);
            
            if (ul.Length > 0) { User u = ul[0]; Node n = new Node(); found = (n.CreatorID == u.Id); }
            return found;
        }

        public static bool IsValidEmail(string email) {
            Regex r = new Regex(@"^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$");
            return (string.IsNullOrEmpty(email) ? false : r.IsMatch(email));
        }

        public static int CreateComment(int parentId) {
            HttpRequest post = HttpContext.Current.Request;

            string email = post["email"];
            string comment = post["comment"];
            string name = post["author"];
            string website = post["url"];

            if (!string.IsNullOrEmpty(email) && BlogLibrary.IsValidEmail(email) && !string.IsNullOrEmpty(comment) && !string.IsNullOrEmpty(name)) {
                bool isSpam = false;

                SpamChecker checker = Config.GetChecker();
                if (checker != null) isSpam = checker.Check(parentId, post.UserAgent, post.UserHostAddress, name, email, website, comment);
                // If spam, save, but do not publish;
                // If !spam, save, and if (auto-publish == true) then publish;
                // (Save is automatically done within the POCO).
                try
                {
                    Comment c = new Comment(parentId, name, email, website, comment)
                    {
                        IsSpam = isSpam
                    };

                    // Don't publish if Comments are being Moderated or are marked as Spam.
                    if (!c.Blog.ModerateComments || !isSpam)
                        c.Publish();
                    return 1;
                }
                catch (InvalidOperationException ioe)
                {
                    // 
                }
            }
            //if nothing gets created, we return zero
            return 0;
        }

        public static int FindParentId(string contentType, int nodeId) {
            Node n = new Node(nodeId);
            if (!string.IsNullOrEmpty(n.Path))
            {
                foreach (var strId in n.Path.Split(",".ToCharArray()))
                {
                    int id = int.Parse(strId);
                    if (id > -1 && BlogLibrary.IsType(contentType, id))
                    {
                        return id;
                    }
                }
            }
            else
            {
                // Document isn't published yet...
                CMSNode doc = new CMSNode(nodeId);
                while (doc.ParentId > -1 && !BlogLibrary.IsType(contentType, doc.ParentId))
                {
                    doc = doc.Parent;
                }
                return doc.ParentId;
            }
            return -1;
        }
        
        public static string GetValueRecursively(string alias, int nodeId) {
            Document n = new Document(nodeId);
            var p = n.getProperty(alias);
            if (p != null && !string.IsNullOrEmpty(p.Value.ToString())) return p.Value.ToString();
            else if (n.Level > 1) return GetValueRecursively(alias, n.Parent.Id);
            return string.Empty;
        }

        public static int CountSeparatedStringMatches(string input1, string separator1, string input2, string separator2) {
            string[] taggroup1 = input1.Split(new string[] { separator1 }, StringSplitOptions.RemoveEmptyEntries);
            string[] taggroup2 = input2.Split(new string[] { separator2 }, StringSplitOptions.RemoveEmptyEntries);

            int score = 0;
            foreach (string srctag in taggroup1) {
                foreach (string targettag in taggroup2) {
                    if (srctag == targettag) score++;
                }
            }
            return score;
        }

        internal static string DocumentTypesAsString(string docType) {
            List<DocumentType> ldt = BlogLibrary.TypeList(docType, "down");
            string retVal = string.Empty;
            string formatRet = "SELECT '{0}' AS Alias";
            string union = " UNION ALL ";
            foreach (DocumentType d in ldt) {
                retVal += string.Format(formatRet, d.Alias);
                if (ldt.IndexOf(d) != (ldt.Count - 1)) retVal += union;
            }
            return retVal;
        }

        public static XPathNodeIterator GetComments() {
            ISqlHelper SqlHelper = DataLayerHelper.CreateSqlHelper(umbraco.GlobalSettings.DbDSN);
            IRecordsReader rr = SqlHelper.ExecuteReader("select * from comment where spam != 1");
            return CommentsToXml(rr);
        }

        public static XPathNodeIterator GetCommentsForBlog(int id)
        {
            return Blog.XmlComments(id);
        }

        /// <summary>
        /// Retrieves all comments in the blog, optionally flattening them to a single level.
        /// </summary>
        /// <remarks>Currently used by the CommentModeration form.</remarks>
        /// <param name="id"></param>
        /// <param name="flattenComments"></param>
        /// <returns></returns>
        public static IEnumerable<Comment> GetCommentsForBlog(int id, bool flattenComments = false, string filter = "")
        {
            Blog blog = null;
            try
            {
                blog = new Blog(id);
            }
            catch
            {
            }
            if (blog != null)
            {
                foreach (var comment in blog.Comments)
                {
                    if (string.IsNullOrWhiteSpace(filter) || filter == "ham" && !comment.IsSpam || filter == "spam" && comment.IsSpam)
                        yield return comment;
                    if (flattenComments)
                    {
                        foreach (var cc in GetFlattenedCommentsInternal(comment)
                            .Where(c => string.IsNullOrWhiteSpace(filter) || filter == "ham" && !c.IsSpam || filter == "spam" && c.IsSpam))
                            yield return cc;
                    }
                }
            }
        }
        
        private static IEnumerable<Comment> GetFlattenedCommentsInternal(Comment comment) {
            foreach (var c in comment.Comments)
            {
                yield return c;
                foreach (var cc in GetFlattenedCommentsInternal(c))
                    yield return cc;
            }
        }
        public static XPathNodeIterator GetCommentsForPost(int id) {
            return Post.XmlComments(id);
        }

        private static XPathNodeIterator CommentsToXml(IRecordsReader rr) {
            XmlDocument xd = new XmlDocument();
            XmlNode x = umbraco.xmlHelper.addTextNode(xd, "comments", "");
            while (rr.Read()) {
                XmlNode c = xd.CreateElement("comment");

                c.Attributes.Append(umbraco.xmlHelper.addAttribute(xd, "id", rr.GetInt("id").ToString()));
                c.Attributes.Append(umbraco.xmlHelper.addAttribute(xd, "nodeid", rr.GetInt("nodeid").ToString()));
                c.Attributes.Append(umbraco.xmlHelper.addAttribute(xd, "created", rr.GetDateTime("created").ToString()));

                c.AppendChild(umbraco.xmlHelper.addCDataNode(xd, "name", rr.GetString("name")));
                c.AppendChild(umbraco.xmlHelper.addCDataNode(xd, "email", rr.GetString("email")));
                c.AppendChild(umbraco.xmlHelper.addCDataNode(xd, "website", rr.GetString("website")));
                c.AppendChild(umbraco.xmlHelper.addCDataNode(xd, "message", rr.GetString("comment")));

                x.AppendChild(c);
            }
            xd.AppendChild(x);
            rr.Close();
            return xd.CreateNavigator().Select(".");
        }

        private static XPathNodeIterator GetIteratorFromString(string xmlstring) {
            XmlDocument xd = new XmlDocument();
            xd.LoadXml(xmlstring);
            XPathNavigator xp = xd.CreateNavigator();
            return xp.Select("/");
        }

        public static XPathNodeIterator GetSkin(string cssFilePath) {
            XmlDocument document;
            try {
                var server = System.Web.HttpContext.Current.Server;
                var cssFilePhysicalPath = server.MapPath(cssFilePath);

                if (!File.Exists(cssFilePhysicalPath)) {
                    document = new XmlDocument();
                    document.LoadXml(string.Format("<error>Could not find the CSS file '{0}'</error>", cssFilePath));
                    return document.CreateNavigator().Select("/");
                }

                var skinFilePath = Path.Combine(Path.GetDirectoryName(cssFilePhysicalPath), "skin.xml");
                if (!File.Exists(skinFilePath)) {
                    document = new XmlDocument();
                    document.LoadXml(string.Format("<error>Could not find the skin file '{0}'</error>", skinFilePath));
                    return document.CreateNavigator().Select("/");
                }

                document = new XmlDocument();
                document.Load(skinFilePath);
                return document.CreateNavigator().Select("/skin");
            } catch (Exception exception) {
                document = new XmlDocument();
                document.LoadXml(string.Format("<error><message>{0}</message><stackTrace>{1}</stackTrace></error>", exception.Message, exception.StackTrace));
                return document.CreateNavigator().Select("/");
            }
        }

        public static string GetUrlContents(string url) { return new StreamReader(BlogLibrary.GetUrlStream(url), true).ReadToEnd(); }
        public static Stream GetUrlStream(string url) {
            HttpWebRequest hrq = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse hrp = (HttpWebResponse)hrq.GetResponse();
            if (hrp.StatusCode == HttpStatusCode.OK) return hrp.GetResponseStream();
            return null;
        }
    }
}
